#include <stdio.h>
#include "simd.h"
//#include "athread.h"
#include "task.h"
#include "slave.h"
#include "reg_transfer.h"
#include "ldm_privatequeue.h"


#ifdef TARG_SW9
//#define CHIP_BUFF_SIZE 8
//struct task_chip_buff
//{
//    struct task* base[CHIP_BUFF_SIZE];
//    volatile int head;
//    volatile int tail;
//};
#if defined(_SERIAL_QUEUE)
extern __thread_local_fix struct aceMesh_stack *my_private_queue;
#endif
#ifdef _CIRCULAR_QUEUE 
extern __thread_local_fix struct task **my_mqueue_base;
#elif !defined( _LDMQ)
//for disable ldmq
      #ifdef LOCAL_MULTI_PRIORI
// using multiple task queues to simulate multiple priorities queue
extern __thread_local_fix struct aceMesh_stack *my_private_queue[LOCAL_MULTI_SIZE];
      #else
extern __thread_local_fix struct aceMesh_stack *my_private_queue;
      #endif

#endif
__thread_local_fix int sent_steal_req=1;
__thread_local_fix int recv_num=1;
extern __thread_local_fix int local_total_num_threads;
__thread_local_fix struct task_chip_buff all_chip_buff[64];
extern struct generic_scheduler* schedulers;
#define remote_ldm(coreid,ldm_var) ((unsigned long)&ldm_var | ((unsigned long)coreid << 20 ) |(1ULL << 45))
#define remote_ldm_addr(coreid,ldm_var_addr) ((unsigned long)ldm_var_addr | ((unsigned long)coreid << 20 ) |(1ULL << 45))

#ifdef EMP_PROFILING
__thread_local_fix unsigned long once_send_time=0,once_recv_time=0,temp=0,rma_time=0;
#endif


void sendtask2PE(struct task* t, int destid)
{
#ifdef EMP_PROFILING
    temp=rtc();
#endif
    struct task_chip_buff* self=remote_ldm(destid,(all_chip_buff[_MYID])); 
#ifdef EMP_PROFILING
   rma_time=rtc()-temp;
#endif
    int local_tail=(self->tail+1)%CHIP_BUFF_SIZE;
    while(self->head == local_tail)
        recvtaskfromRegNT();
    self->base[self->tail]=t;
    self->tail=local_tail;
    self->send_num=self->send_num+1;
#ifdef EMP_PROFILING
   once_send_time=rtc()-temp;
#endif
}

void init_chip_queue()
{
    int i=0;
    for(i=0;i<local_total_num_threads;i++)
    {
        all_chip_buff[i].tail=0;
        all_chip_buff[i].head=0;
    }
}

//pop interface, for consumer
////local access
int slave_chip_try_pop(struct task_chip_buff* self, unsigned long** t)
{
    int local_head=self->head;
    if(self->tail == local_head) return 0;
    *t=self->base[local_head];
    self->head=(local_head+1)%CHIP_BUFF_SIZE;
    return 1;
}

#ifdef EMP_RANDOM_STEALING
struct task*  emp_random_stealing()
{
    unsigned long seed;
    int destid, iter = 0;
    if (sent_steal_req) return NULL;
     seed = (rtc() + _MYID);
    destid = seed % local_total_num_threads;
    struct task_chip_buff *self;
   if (destid != _MYID) {
            self = remote_ldm(destid, (all_chip_buff[_MYID]));
        } else {
            destid=(destid+1)%local_total_num_threads;
            self =
                remote_ldm(destid, (all_chip_buff[_MYID]));
        }
   // self->steal = MSG_STEAL_REQ;
    sent_steal_req = 1;
   return NULL;
}
#endif
//for all thread, clear RegNT
__thread_local_fix int times=0;
int recvtaskfromRegNT()
{
#ifdef EMP_PROFILING
    temp=rtc();
    times++;
#endif
    int i=0,j=0;
    struct task_chip_buff* self=NULL;
    struct task* t=NULL;
    int local_head=0;
    generic_scheduler *that_scheduler = NULL;
    for(i=0;i<local_total_num_threads;)
    {
        //slave_chip_try_pop(self,&t);
        self=&all_chip_buff[i];
        if((self->send_num-self->recv_num >2)||(recv_num&&(self->send_num!=self->recv_num))){
        local_head=self->head;
        if(self->tail == local_head)
        {
            i++;
            continue;
        }
        t=self->base[local_head];
        self->head=(local_head+1)%CHIP_BUFF_SIZE;
        self->recv_num=self->recv_num+1;
#ifdef SEG_BUFF
#ifdef AGENT_COMPU 
        if(t->affinity_id>-1 && t->affinity_id<=local_total_num_threads-N_SLAVE_AGENT)
#else
        if(t->affinity_id>-1 && t->affinity_id<local_total_num_threads-N_SLAVE_AGENT)
#endif
#else
        if(t->affinity_id>-1 && t->affinity_id<local_total_num_threads)
#endif
#ifdef _LDMQ
                slave_ldmq_push(t);
#else
#ifdef _CIRCULAR_QUEUE
                slave_push(my_mqueue_base, t);
#else
                slave_push(my_private_queue, t);
#endif
#endif
#ifdef SEG_BUFF 
        else if(t->affinity_id==local_total_num_threads)
        {
            that_scheduler=&schedulers[local_total_num_threads];
            slave_push(&(that_scheduler->buff_spawn),t);
            that_scheduler = NULL;
        }
#endif
#ifdef TEMP_ASSERT
        else
        {
            printf("task=%p,affinity_id=%d,my_id=%d\n",t,t->affinity_id,_MYID);
            assert(0);
        }
#endif
   }else{
    i++;
    }
    }
#ifdef EMP_PROFILING
   if(times==10)
   once_recv_time=rtc()-temp;
#endif
}
#ifdef SEG_BUFF
//only for slave_agent thread when agent not compute, clear RegNT
void slave_clear_RegNT_to_need_spawn()
{
    int i=0,j=0;
    struct task_chip_buff* self=NULL;
    struct task* t=NULL;
    int local_head=0;
    generic_scheduler  *that_scheduler = &schedulers[local_total_num_threads];
    for(i=0;i<local_total_num_threads;)
    {    
        self=&all_chip_buff[i];
        if((self->send_num-self->recv_num>2)||(recv_num&&(self->send_num!=self->recv_num))){ 
        local_head=self->head;
        if(self->tail == local_head)
        {
            i++;
            continue;
        }
        t=self->base[local_head];
        self->head=(local_head+1)%CHIP_BUFF_SIZE;
        self->recv_num=self->recv_num+1;
        slave_push(&(that_scheduler->buff_spawn),t);
    }else
    {
        i++;
    } 
    }    
}
#endif

#else
  #if defined(_CIRCULAR_QUEUE) || defined(_SERIAL_QUEUE)
#ifdef TARG_SW3
#define doublev4 doublev2  
#endif

    #ifdef _CIRCULAR_QUEUE 
extern __thread_local_fix struct task **my_mqueue_base;
    #elif !defined( _LDMQ)
//for disable ldmq
      #ifdef LOCAL_MULTI_PRIORI
// using multiple task queues to simulate multiple priorities queue
extern __thread_local_fix struct aceMesh_stack *my_private_queue[LOCAL_MULTI_SIZE];
      #else
extern __thread_local_fix struct aceMesh_stack *my_private_queue;
      #endif

    #endif


extern __thread_local_fix int my_id;
#ifdef _BLACKROW
extern int black_topo;
#endif

#define RegNT_full(dir)  csr_put_full()

#define RegNT_empty(dir) \
	((dir==DROW)?csr_getr_empty(): csr_getc_empty())


    #ifdef EMP_RANDOM_STEALING
#define MSG_STEAL_REQ   0xababab
#define MSG_STEAL_REPLY 0xbababa
__thread_local_fix int sent_steal_req = 0;
__thread_local_fix unsigned always_fail = 0;
        #ifdef LOCAL_MULTI_PRIORI
extern __thread_local_fix int mqueue_empty[LOCAL_MULTI_SIZE]; 
extern __thread_local_fix unsigned short ldmq_full[LOCAL_MULTI_SIZE];
extern __thread_local_fix unsigned short top[LOCAL_MULTI_SIZE];  //available
extern __thread_local_fix unsigned short bottom[LOCAL_MULTI_SIZE];
extern __thread_local_fix struct task* ldmqueue[LOCAL_MULTI_SIZE][LDM_QUEUE_SIZE];
        #else
extern __thread_local_fix int mqueue_empty; 
extern __thread_local_fix unsigned short ldmq_full;
extern __thread_local_fix unsigned short top;  //available
extern __thread_local_fix unsigned short bottom;
extern __thread_local_fix struct task* ldmqueue[LDM_QUEUE_SIZE];
        #endif
            #ifndef _LDMQ
#include "aceMesh_stack.h"
            #endif
        #ifdef EMP_STEALING_PROF
__thread_local_fix unsigned steal_fail = 0;
__thread_local_fix unsigned steal_success = 0;
        #endif
    #endif

//empty:1; no-empty:0
unsigned int csr_getr_empty()
{
    unsigned int tmp;
    asm volatile("rcsr %0,0x34":"=r"(tmp));
    return tmp;
}

//empty:1; no-empty:0
unsigned int csr_getc_empty()  
{    
    unsigned int tmp;    
    asm volatile("rcsr %0,0x35":"=r"(tmp));
    return tmp;
}
//full:1; no-full:0
unsigned int csr_put_full()  
{    
    unsigned int tmp;    
    asm volatile("rcsr %0,0x36":"=r"(tmp));
    return tmp;
}
int get_route(int scheid, int destid, int *dir, int *route)
{
    int scheid_row,scheid_col;
    int destid_row,destid_col;
    scheid_row = scheid / PE_NCOL;
    scheid_col = scheid % PE_NCOL;
    destid_row = destid / PE_NCOL;
    destid_col = destid % PE_NCOL;
    
    if (scheid_row == destid_row)
    {
        *dir = DROW;
        *route = destid_col;
        return 1;
    }
    else if (scheid_col == destid_col)
    {
        *dir = DCOL;
        *route = destid_row;
        return 1;
    }
    else 
    {
        //printf("%d and %d are not in same row or col\n",scheid,destid);
        return 0;
        //exit(1);
    }
}

void get_media(int my_id, int dest_id, int *dir, int *route)
{
#ifdef _BLACKROW
    if (( my_id / PE_NROW ) == black_topo && (my_id % PE_NCOL) != (dest_id % PE_NCOL) )
    {
        *dir = DROW;
        *route = dest_id % PE_NCOL;	
        //printf("%d",*dir);
    }
    else 
    {
        *dir = DCOL;
        *route = black_topo;
    }
#else
    //*dir=DCOL;
    *dir=(rtc()%2);
    *route=dest_id/PE_NCOL;
#endif
}

/*
struct task* recv_one_task(int dir)
{
    struct task* tt[4] ;
    doublev4 *p, dum;
    tt[0]=tt[1]=tt[2]=tt[3]=NULL;
    p=(doublev4 *)(&(tt[0]));
#ifdef DEBUG_GRAPH 
   assert(dir==ROW || dir==COL);
#endif
    //while (RegNT_empty(dir));
    //{
        if ( dir == ROW )
        {
            *p = simd_getr(dum);
            //break;
        }
        else if (dir == COL)
        {
            *p = simd_getc(dum);
            //break;
        }
    //}
    //printf("%d recv one %d\n",my_id,tt[0]->affinity_id);
	//simd_fprint_doublev4(stderr, dum);
    return tt[0];
}*/


#ifdef ACEMESH_PROFILING_SUCC
//extern unsigned long all_spawn_slave_task_time[65];
//extern unsigned long all_spawn_slave_trans1_time[65];
//extern unsigned long all_spawn_slave_trans2_time[65];
//extern unsigned long all_spawn_master_task_time[65];
extern __thread_local_fix unsigned long local_reg_put_time;
extern __thread_local_fix unsigned int local_reg_put_full_times,total_local_reg_put_times;
#endif

#if defined( ASYN_INSERT_Q ) || defined( DELAYED_SEND )
extern void asyn_insert_to_task_q(doublev4* t);
#endif

void sendvec(int dir, int route, doublev4* t)
{
#ifdef ACEMESH_PROFILING_SUCC
    //unsigned long local_count=0;
    unsigned long send_start;
#endif

    int nrecv;
    nrecv=recvtaskfromRegNT();
#if defined( ASYN_INSERT_Q ) 
    doublev4 *p1=NULL;
    struct task* tt[4];
    p1 =(doublev4 *)(&(tt[0]));
    *p1 = simd_vextf0(*t);
#endif
    if(dir == DROW)
    {
#ifdef ACEMESH_PROFILING_SUCC
        total_local_reg_put_times++;
        send_start=rtc();
        if(csr_put_full())local_reg_put_full_times++;
#endif
#if defined( ASYN_INSERT_Q ) || defined( DELAYED_SEND )
        if(!csr_put_full())
            simd_putr(*t, route);
        else
        {
            asyn_insert_to_task_q(tt[0]);
        }
#else
        simd_putr(*t, route);
#endif
#ifdef ACEMESH_PROFILING_SUCC
        local_reg_put_time+=rtc()-send_start;
#endif
    }
    else if ( dir == DCOL )
    {   
#ifdef ACEMESH_PROFILING_SUCC
        total_local_reg_put_times++;
        send_start=rtc();
        if(csr_put_full()) local_reg_put_full_times++;
#endif
#if defined( ASYN_INSERT_Q ) || defined( DELAYED_SEND )
        if(!csr_put_full())
            simd_putc(*t, route);
        else
        {
            asyn_insert_to_task_q(tt[0]);
        }
#else
        simd_putc(*t, route);
#endif
        //simd_fprint_doublev4(stderr, t);
#ifdef ACEMESH_PROFILING_SUCC
        local_reg_put_time+=rtc()-send_start;
#endif
    }
    //nrecv=recvtaskfromRegNT(); 1216
}

#if defined(_SERIAL_QUEUE)
extern __thread_local_fix struct aceMesh_stack *my_private_queue;
#endif
struct task* recv_one_task(int dir)
{
	long destid;
    int dir1,route;
    struct task* tt[4];
    struct task* tm[4];//={0};
    doublev4 *p1=NULL;
    doublev4 *p2=NULL;
    tt[0]=tt[1]=tt[2]=tt[3]=NULL;
    tm[0]=tm[1]=tm[2]=tm[3]=0;
    p1 =(doublev4 *)(&(tt[0]));
    p2 =(doublev4 *)(&(tm[0]));

#ifdef DEBUG_GRAPH 
    assert(dir==DROW || dir==DCOL);
#endif
    if ( dir == DROW )
        *p2 = simd_getr(*p1);  //store to p2, p1 is just a meningless padding parameter;
    else if (dir == DCOL)
        *p2 = simd_getc(*p1);
    //simd_fprint_doublev4(stderr, *p2);
    #ifdef EMP_RANDOM_STEALING
    if (tm[0] == MSG_STEAL_REQ) {
        struct task * t = NULL;
        int succ;
        #ifdef _LDMQ
        slave_ldmq_trypop(t, succ);
        #else
        if(slave_try_pop(my_private_queue, &t))
        { 
           succ=1;
        }
        //printf("error: not serial queue\n");
        #endif
        if(succ)
        {
          destid = tm[1];
          tm[0] = MSG_STEAL_REPLY;
          tm[1] = _MYID;
          tm[2] = (long)t;
          tm[3] = 0;

          if (get_route(_MYID, destid, &dir1, &route)) {   
            sendvec(dir1, route, p2);
            return NULL;
          }
          else {
            printf("reply steal req error\n");
          }
        }
    }
    else if (tm[0] == MSG_STEAL_REPLY) {
        sent_steal_req = 0;
        if (tm[2]) {
            always_fail = 0;
        #ifdef EMP_STEALING_PROF
            steal_success++;
        #endif
        }
        else {
            always_fail++;
        #ifdef EMP_STEALING_PROF
            steal_fail++;
        #endif
        }
        return tm[2];
    }
    else {

    #endif

    destid=tm[1];
    //printf("dest_id%d",tm[1]);
    if (destid == my_id)
    {
        *p1 = simd_vextf0(*p2);
        //printf("ll%ld",tt[0]->id);
        //printf("a%d",my_id);
        return tt[0];
    }
    else if ( get_route(my_id, destid, &dir1, &route) )	
    {   
        sendvec(dir1, route, p2);
        //sendvec(dir1, route, p2);
        //printf("b%d",my_id);
        return NULL;
    }
    else{
        get_media(my_id, destid, &dir1, &route);
        sendvec(dir1,route,p2);
        //sendvec(dir1,route,p2);
        //printf("%d",my_id);
        //printf("%d",dir1);
        //printf("%d",route);
        return NULL;
    }

    #ifdef EMP_RANDOM_STEALING
    }
    #endif
}

/*void sendtask(int dir, int route, struct task* p)
{
    doublev4 *t,std;
    t = (doublev4 *)&p;
#ifdef DEBUG_GRAPH 
   assert(dir==ROW || dir==COL);
#endif
    if ( dir == ROW )
    {
        simd_putr(*t, route);
       // simd_fprint_doublev4(stderr, std);
    }
    else // dir == COL )
    { 
        simd_putc(*t, route);
        //simd_fprint_doublev4(stderr, std);
    }

}*/

#ifdef ACEMESH_PROFILING_SUCC
extern __thread_local_fix unsigned int local_reg_direct_times,local_reg_transfer_times;
#endif

void sendtask2PE(struct task* p, int destid)
{
    long tmp[4];//={0};
    int dir,route;
    doublev4 *t;

    tmp[0]=p;
    tmp[1]=destid;
    tmp[2]=tmp[3]=0;
    //printf("dest_id%d",tmp[1]);
    //printf("id%d",p->id);
    t=(doublev4 *)&tmp;
    
#ifdef TEMP_ASSERT
    if(p->affinity_id != destid) assert(0);
#endif

    if ( get_route(my_id, destid, &dir, &route) )
    {
        sendvec(dir, route, t);
#ifdef ACEMESH_PROFILING_SUCC
        local_reg_direct_times++;
#endif
    }
    else
    {
        get_media(my_id, destid, &dir, &route);
        //printf("dir%d",dir);
        //printf("route%d",route);
        sendvec(dir, route, t);
        //simd_fprint_doublev4(stderr, *t);
#ifdef ACEMESH_PROFILING_SUCC
        local_reg_transfer_times++;
#endif
    }
}

int recvtaskfromRegNT()
{
    int ret = 0;
    struct task *p = NULL;

    while (!RegNT_empty(DROW) || !RegNT_empty(DCOL)) {
        if (!RegNT_empty(DROW)) {
            p = recv_one_task(DROW);
            if (p != NULL) {
                ret++;
#ifdef _LDMQ
#ifdef DEBUG
        printf("22222222222\n");
#endif
                slave_ldmq_push(p);
#else
#ifdef _CIRCULAR_QUEUE
                slave_push(my_mqueue_base, p);
#else
                slave_push(my_private_queue, p);
#endif
#endif
            }
            //    }
        }
        //    while(!RegNT_empty(DCOL))
        //    {
        if (!RegNT_empty(DCOL)) {   //这里的while相当于存在某种序关系，会不会导致列向消息严重延迟
            p = recv_one_task(DCOL);
            if (p != NULL) {
                ret++;
#ifdef _LDMQ
                slave_ldmq_push(p);
#else
#ifdef _CIRCULAR_QUEUE
                slave_push(my_mqueue_base, p);
#else
                slave_push(my_private_queue, p);
#endif
#endif
            }
        }
    }
    // if(ret) printf("%d recv %d from NoC!\n",my_id,ret);
    return ret;
}

    #ifdef EMP_RANDOM_STEALING
int get_stealing_id(int selfid, int index)
{
    int selfrow, selfcol;
    int destrow, destcol;

    selfrow = selfid / 8;
    selfcol = selfid % 8;
    destrow = selfrow;
    destcol = selfcol;

    if (index < 7) {
        destcol = (selfcol + index + 1) % 8;
    }
    else {
        int row_idx = index - 7;
        destrow = (selfrow + row_idx + 1) % 8;
    }

    return destrow * 8 + destcol;
}

void emp_random_stealing()
{
    unsigned long seed;
    int index, destid;
    long tmp[4];//={0};
    int dir,route;
    doublev4 * msg;

    if (sent_steal_req)
        return;
    
    seed = (rtc()+_MYID);
    index = seed % 14; //same row && same col without itself
    destid = get_stealing_id(_MYID, index);

    tmp[0] = MSG_STEAL_REQ;
    tmp[1] = _MYID;
    tmp[2] = 0;
    tmp[3] = 0;
    msg = (doublev4 *)&tmp;
    
    if (get_route(_MYID, destid, &dir, &route))
        sendvec(dir, route, msg);
    else
        printf("error sending steal req\n");
    
    sent_steal_req = 1;
}
    #endif

#endif

#endif
